﻿using Codice.CM.Client.Differences.Graphic;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using static UnityEditor.Experimental.GraphView.GraphView;

namespace ttbit.form.unity
{

    class Graphic : IGraphic
    {
        public Graphic(UnityEngine.Camera camera, UnityEngine.Transform rootTran)
        {
            this.rootTran = rootTran;
            mainCamera = camera;
            TextureWhite = new Texture2D(UnityEngine.Texture2D.whiteTexture);
            MainScreen = new RenderTarget(null, this);
            srcwidth = MainScreen.width;
            srcheight = MainScreen.height;
        }
        UnityEngine.Camera mainCamera;
        UnityEngine.Transform rootTran;
        public ITexture TextureWhite
        {
            get;
            private set;
        }

        public IRenderTarget MainScreen
        {
            get;
            private set;
        }
        int srcwidth;
        int srcheight;
        public event Action<int, int> OnChangeSize;
        public event Action<float> OnUpdate;
        public event Action<IRenderTarget> OnRender;
        public event Action OnPreRender;

        public List<IRenderTarget> allrt = new List<IRenderTarget>();
        public IRenderTarget CreateRenderTarget(int w, int h, TextureFormat format)
        {
            UnityEngine.RenderTextureFormat uformat = UnityEngine.RenderTextureFormat.R8;
            if (format == TextureFormat.R8)
            {
                uformat = UnityEngine.RenderTextureFormat.R8;
            }
            else if (format == TextureFormat.RGBA32)
            {
                uformat = UnityEngine.RenderTextureFormat.ARGB32;
            }
            else
            {
                throw new Exception("not supported.");
            }
            var rt = new UnityEngine.RenderTexture(w, h, 0, UnityEngine.RenderTextureFormat.ARGB32);
            var nrt = new RenderTarget(rt, this);
            allrt.Add(nrt);

            return nrt;
        }



        public ITexture CreateStaticTexture(int w, int h, TextureFormat format, byte[] data)
        {
            UnityEngine.TextureFormat uformat = UnityEngine.TextureFormat.Alpha8;
            if (format == TextureFormat.R8)
            {
                uformat = UnityEngine.TextureFormat.R8;
            }
            else if (format == TextureFormat.RGBA32)
            {
                uformat = UnityEngine.TextureFormat.RGBA32;
            }
            var tex = new UnityEngine.Texture2D(w, h, uformat, false, false);
            tex.filterMode = FilterMode.Point;
            tex.SetPixelData(data, 0, 0);

            tex.Apply(false, false);
            return new Texture2D(tex) { isStatic = true };
        }

        public ITexture CreateDynamicTexture(int w, int h, TextureFormat format)
        {
            UnityEngine.TextureFormat uformat = UnityEngine.TextureFormat.Alpha8;
            if (format == TextureFormat.R8)
            {
                uformat = UnityEngine.TextureFormat.R8;
            }
            else if (format == TextureFormat.RGBA32)
            {
                uformat = UnityEngine.TextureFormat.RGBA32;
            }
            var tex = new UnityEngine.Texture2D(w, h, uformat, false, false);
            tex.filterMode = FilterMode.Point;
            return new Texture2D(tex) { isStatic = false };
        }
        void __UploadTexture(ITexture tex, int w, int h, byte[] data)
        {
            Texture2D t2d = tex as Texture2D;
            if (t2d == null)
                throw new Exception("not a texture");
            if (w != t2d.tex.width || h != t2d.tex.height)
            {
                t2d.tex.width = w;
                t2d.tex.height = h;
            }
            if (tex.format == TextureFormat.RGBA32)
            {
                t2d.tex.SetPixelData(data, 0, 0);
            }
            else
            {
                t2d.tex.SetPixelData(data, 0, 0);
            }
        }



        public void UploadTexture(ITexture tex, int x, int y, int w, int h, byte[] data)
        {
            Texture2D t2d = tex as Texture2D;
            if (t2d == null)
                throw new Exception("not a texture");
            if (t2d.isStatic)
            {
                throw new Exception("static texture can not upload.");
            }
            if (x == 0 && y == 0 && w == t2d.width && h == t2d.height)
            {
                __UploadTexture(tex, w, h, data);
            }
            else
            {
                __UploadSubTexture(tex, x, y, w, h, data);
            }
        }

        public void ApplyTexture(ITexture tex, bool markNotReadable = false)
        {

            Texture2D t2d = tex as Texture2D;
            if (t2d == null)
                throw new Exception("not a texture");
            if (t2d.isStatic)
                throw new Exception("static texture can not apply.");
            t2d.tex.Apply(false, markNotReadable);
            if (markNotReadable)
            {
                t2d.isStatic = true;
            }
        }
        void __UploadSubTexture(ITexture tex, int x, int y, int w, int h, byte[] data)
        {
            Texture2D t2d = tex as Texture2D;
            if (t2d == null)
                throw new Exception("not a texture");
            if (t2d.tex.isReadable == false)
                throw new Exception("not support uploadsub here");
            var rdata = t2d.tex.GetRawTextureData<byte>();
            for (var j = 0; j < h; j++)
            {
                for (var i = 0; i < w; i++)
                {
                    int xs = x + i;
                    int ys = y + j;
                    if (xs < t2d.width && ys < t2d.height)
                    {
                        int index = ys * t2d.width + xs;
                        int indexsrc = (j * w + i);
                        if (tex.format == TextureFormat.RGBA32)
                        {
                            rdata[index * 4 + 0] = data[indexsrc * 4 + 0];
                            rdata[index * 4 + 1] = data[indexsrc * 4 + 1];
                            rdata[index * 4 + 2] = data[indexsrc * 4 + 2];
                            rdata[index * 4 + 3] = data[indexsrc * 4 + 3];
                        }
                        else
                        {
                            rdata[index] = data[indexsrc];
                        }
                    }
                }
            }
            t2d.tex.LoadRawTextureData<byte>(rdata);
        }






        public void OneFrame()
        {
            //UserUpdate
            {
                OnUpdate?.Invoke(Time.deltaTime);
            }

            if (srcwidth != MainScreen.width || srcheight != MainScreen.height)
            {
                srcwidth = MainScreen.width;
                srcheight = MainScreen.height;
                OnChangeSize?.Invoke(srcwidth, srcheight);
            }

            foreach (var l in layers)
            {
                l.Value.Clear();
            }

            //User Render
            foreach (var rt in allrt)
            {
                BeginRender(rt);
                OnRender?.Invoke(rt);
                EndRender();
            }
            BeginRender(MainScreen);
            OnRender?.Invoke(MainScreen);
            EndRender();

            EndRender();

        }

        Dictionary<uint, DrawLayer> layers = new Dictionary<uint, DrawLayer>();
        DrawLayer currentLayer = null;
        public void BeginRender(IRenderTarget target)
        {
            if (target == null)
                target = MainScreen;
            if (!layers.TryGetValue(target.ID, out DrawLayer layer))
            {
                var camera = mainCamera;

                string tranname = "layer_" + ((target == MainScreen) ? "root" : target.ID);

                UnityEngine.Transform tran = rootTran.Find(tranname);
                if (tran == null)
                {
                    var tranobj = new UnityEngine.GameObject(tranname);
                    tranobj.transform.parent = rootTran;
                    tran = tranobj.transform;
                }

                layer = new DrawLayer(target, camera, tran);
                layers[layer.ID] = layer;
            }
            currentLayer = layer;
            var ucolor = new UnityEngine.Color(target.ClearColor.R, target.ClearColor.G, target.ClearColor.B, target.ClearColor.A);
            currentLayer.Begin(target);
        }

        public void DrawRect(ITexture tex, Effect effect, RectangleF rect, Rectangle uv, System.Drawing.Color color)
        {

            var urect = new UnityEngine.Rect(rect.X, rect.Y, rect.Width, rect.Height);
            var urectuv = new UnityEngine.Rect(uv.X, uv.Y, uv.Width, uv.Height);
            var ucolor = new UnityEngine.Color32(color.R, color.G, color.B, color.A);

            currentLayer.DrawRect(tex, effect, urect, urectuv, ucolor);
        }

        public void DrawRect(ITexture tex, RectangleF rect, Rectangle uv, System.Drawing.Color color)
        {
            DrawRect(tex, null, rect, uv, color);
        }

        public void DrawRect(ITexture tex, System.Numerics.Vector2 pos, Rectangle uv, System.Drawing.Color color)
        {
            DrawRect(tex, null, new RectangleF(pos.X, pos.Y, uv.Width, uv.Height), uv, color);
        }

        public void EndRender()
        {
            currentLayer.End();
        }



        public void PreRender_DrawTarget(IRenderTarget target)
        {
            throw new NotImplementedException();
        }
        public void ReadPixel(IRenderTarget target, Action<TextureFormat, int, int, byte[]> onReadBack)
        {
            throw new NotImplementedException();
        }

        public void DrawRectFree(ITexture tex, Effect effect, DrawPoint point0, DrawPoint point1, DrawPoint point2, DrawPoint point3)
        {
            currentLayer.DrawRectFree(tex, effect, point0, point1, point2, point3);

        }

        public void DrawTris(ITexture tex, Effect effect, DrawPoint[] tris, int tricount)
        {
            currentLayer.DrawTris(tex, effect, tris, tricount);
        }

        public void DrawLines(ITexture tex, Effect effect, DrawPoint[] lines, int linecount)
        {
            currentLayer.DrawLines(tex, effect, lines, linecount);
        }

        public void DrawPoints(ITexture tex, Effect effect, DrawPoint[] points, int pointcount)
        {
            currentLayer.DrawPoints(tex, effect, points, pointcount);
        }


    }
}
